#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

extern void monocromatizarMax(char* red, char* green, char* blue, unsigned tam, char* matriz);
extern void monocromatizarProm (char* red, char* green, char* blue, unsigned int tam, char* resultado);
extern void umbralizar(char* matriz, unsigned alto, unsigned ancho, unsigned altoAncho, char umbralSup, char umbralInf);
extern void sobel(char* matriz, unsigned alto, unsigned ancho, char* matriz2);
extern void prewitt(char* matriz, unsigned alto, unsigned ancho, char* matriz2);
extern void roberts(char* matriz, unsigned alto, unsigned ancho, char* matriz2);

/*
Opciones de línea de comandos:
   r1 = realzar bordes con el operador de Roberts
   r2 = realzar bordes con el operador de Prewitt
   r3 = realzar bordes con el operador de Sobel
   m1 = monocromatizar con norma p = 1
   m2 = monocromatizar con norma p = ∞
   u = umbralizar (en este caso tambien toma dos enteros para los umbrales maximo y minimo)
   i = input (nombre del archivo)
*/

void help() {
    printf("Parametros:\n");
    printf("--help            (-h) Muestra esta ayuda\n");
    printf("--m1              (-m) Convierte a escala de grises con promedios\n");
    printf("--m2              (-g) Convierte a escala de grises con maximo\n");
    printf("--input           (-i) Nombre de archivo de entrada ");
    printf(" (obligatorio)\n");
    printf("--output          (-o) Nombre de archivo de salida ");
    printf(" (obligatorio)\n");
    printf("--r1              (-r) Deteccion de bordes con el operador de Roberts\n");
    printf("--r2              (-p) Deteccion de bordes con el operador de Prewitt\n");
    printf("--r3              (-s) Deteccion de bordes con el operador de Sobel\n");
    printf("--umbralizar      (-u) Aplicar umbralizacion por histeresis\n");
    printf("                       Obligatorio: limite inferior y superior\n\n");
    printf("Para umbralizar es necesario seleccionar un filtro.\n");
    printf("Por defecto se monocromatiza con promedios.\n");
}


int main(int argc, char **argv) {
   
    printf("  ____  _           _    _               ____                   _       _\n");
    printf(" / ___|| |__   __ _| | _| |_ __ _ _ __  |  _ \\  ___  _ __   ___| |_ ___| | __\n");
    printf(" \\___ \\| '_ \\ / _` | |/ / __/ _` | '__| | | | |/ _ \\| '_ \\ / _ \\ __/ __| |/ /\n");
    printf("  ___) | | | | (_| |   <| || (_| | |    | |_| | (_) | | | |  __/ |_\\__ \\   <\n");
    printf(" |____/|_| |_|\\__,_|_|\\_\\\\__\\__,_|_|    |____/ \\___/|_| |_|\\___|\\__|___/_|\\_\\\n");

    const char* const op_cortas = "hmgrpsi:o:u:" ; // posibles parametros, van con -
    int siguiente_opcion;

    // forma larga para las opciones. En consola van con --
    const struct option op_largas[] = {
                                          {"m1",          0, NULL,  'm'},
                                          {"m2",          0, NULL,  'g'},
                                          {"input",       1, NULL,  'i'},
                                          {"output",      1, NULL,  'o'},
                                          {"r1",          0, NULL,  'r'},
                                          {"r2",          0, NULL,  'p'},
                                          {"r3",          0, NULL,  's'},
                                          {"umbralizar",  1, NULL,  'u'},
                                          {"help",        0, NULL,  'h'},
                                          { NULL,         0, NULL,   0 }
                                      };

    // si no puso parametros, le decimos que necesitamos el archivo
    if (argc == 1) {
        printf("\nERROR: No se especifico una imagen!\n\n");
        help();
        return 1;
    }

    // declaracion de variables
    char* ficheroEntrada = NULL;      // nombre del archivo
    char* ficheroSalida = NULL;
    char metodo='m';            // metodo de monocromatizacion
    char metodoBordes = 'f';    // metodo de deteccion de bordes
    unsigned min, max;          // parametros para umbralizar
    int umbralizo = 0;          // flag de umbralizar

    while (1) {
        // llamamos a la función getopt
        siguiente_opcion = getopt_long (argc, argv, op_cortas, op_largas, NULL);

        if (siguiente_opcion == -1) // no hay mas opciones
            break;

        // para cada parametro, seteamos el flag correspondiente
        switch (siguiente_opcion) {
        case 'm' : /* -m o --m1 */
            metodo = 'm' ;
            break;

        case 'g' : /* -g o --m2 */
            metodo = 'g' ;
            break;

        case 'r' : /* -r o --r1 */
            metodoBordes = 'r' ;
            break;

        case 's' : /* -s o --r2 */
            metodoBordes = 's' ;
            break;

        case 'p' : /* -p o --r3 */
            metodoBordes = 'p' ;
            break;

        case 'u' : /* -u ó --umbralizar */
            umbralizo = 1;
            sscanf(optarg,"%u",&min);

            break;

        case 'i' : /* -i ó --input */
            ficheroEntrada = optarg; /* optarg contiene el argumento de -i */
            break;
        case 'o' : /* -o ó --output */
            ficheroSalida = optarg; /* optarg contiene el argumento de -o*/
            break;

        case 'h' : /* -h ó --help */
            help();
            return 0;

        case -1 : /* No hay más opciones */
            break;

        default : /* Algo más? No esperado. Abortamos */
            printf("Formato de opciones invalido!\n");
            help();
            return 1;
        }
    }


    // Para umbralizar se necesitan 2 parametros. Como getOpt solo toma uno, el otro lo coloca en
    // los no identificados. Para evitar que se ponga mas de uno, pedimos que solo haya un parametro no
    // identificado.

    if(umbralizo && optind == argc -1)
        sscanf(argv[optind],"%d",&max);

    // si hay parametros de mas paramos
    if((!umbralizo && optind < argc) || (umbralizo && optind != argc -1) ) {
        printf("\nERROR: Formato de opciones invalido!\n\n");
        help();
        return 1;
    }

    // abrimos el archivo
    FILE* fichero;
    FILE* salida;
    unsigned int ancho, alto, offset;
    char marca1, marca2;
    if (ficheroSalida == NULL) {
        printf("\nERROR: No se pudo crear el archivo de salida!\n\n");
        return 1;
    }
    fichero = fopen(ficheroEntrada, "rbw+");
    salida = fopen(ficheroSalida, "wb");
    if (fichero == NULL) {
        printf("\nERROR: Archivo no encontrado! Ingreso el nombre correcto?\n\n");
        help();
        return 1;
    }
    if (salida == NULL) {
        printf("\nERROR: No se pudo crear el archivo de salida!\n\n");
        return 1;
    } else {
        marca1 = fgetc(fichero);  // leemos los dos primeros bytes
        marca2 = fgetc(fichero);
        if (marca1 =='B' && marca2 =='M') {     // si son BM, sino paramos

            fseek(fichero, 18, SEEK_SET);       // posicion 18: ancho
            fread(&ancho, 1, 4, fichero);
            fread(&alto, 1, 4, fichero);        // siguiente dato: alto
            fseek(fichero, 10, SEEK_SET);       // tomamos el offset hasta los pixeles
            fread(&offset,1,4,fichero);
            
            short int sumandoextra = (4 - (ancho*3 % 4)) % 4;
            char* azul = malloc(ancho*alto);
            char* rojo = malloc(ancho*alto);
            char* verde = malloc(ancho*alto);
            char* matriz = malloc(ancho*alto);
            char* matriz2 = malloc(ancho*alto);
            int j = 0;
            fseek(fichero, offset, SEEK_SET);

            // leemos la imagen a memoria en 3 matrices separadas
            // que corresponden a los canales rojo, verde y azul
            // para facilitar el procesamiento posterior
            while(j < alto) {
                int i = 0;
                while(i < ancho) {
                    fread(&rojo[j*ancho+i],1,1,fichero);
                    fread(&verde[j*ancho+i],1,1,fichero);
                    fread(&azul[j*ancho+i],1,1,fichero);
                    i++;
                }
                fseek(fichero,sumandoextra, SEEK_CUR);
                j++;
            }


            if(metodo == 'm') {
                printf("Monocromatizando (Promedios)...\n");
                monocromatizarProm(rojo, verde, azul, ancho*alto, matriz);

            } else if(metodo == 'g') {
                printf("Monocromatizando (Maximos)...\n");
                monocromatizarMax(rojo, verde, azul, ancho*alto, matriz);
            }
            free(azul);
            free(rojo);
            free(verde);
            if(metodoBordes  == 's') {
                printf("Aplicando Sobel...\n");
                sobel(matriz, alto, ancho, matriz2);
                free(matriz);
            } else if (metodoBordes == 'r') {
                printf("Aplicando Roberts...\n");
                roberts(matriz, alto, ancho, matriz2);
                free(matriz);
            } else if (metodoBordes == 'p') {
                printf("Aplicando Prewitt...\n");
                prewitt(matriz, alto, ancho, matriz2);
                free(matriz);
            }
            if(umbralizo && metodoBordes == 'f') {
                printf("\nERROR: Para umbralizar se debe aplicar un filtro primero!");
                printf("\n       Seleccione un filtro para poder umbralizar.\n\n");
                help();
                umbralizo = 0;
            }
            if(umbralizo) { // llamada a umbralizar
                if(min > 255) {
                    printf("Advertencia: los valores mayores a 255 seran interpretados como 255!\n");
                    min = 255;
                }
                if(max > 255) {
                    printf("Advertencia: los valores mayores a 255, seran interpretados como 255!\n");
                    max = 255;
                }

                printf("Umbralizando con umbral superior %u y umbral inferior %u ...\n", max, min);
                umbralizar(matriz2, alto, ancho, alto*ancho, max, min);
            }

            // guardo la matriz en el archivo de salida
            int i = 0;
            char * info = (char*) malloc(offset);
            fseek(fichero, 0, SEEK_SET);
            fread(info,offset,1,fichero);
            fwrite(info, offset, 1, salida);
            fseek(salida, offset, SEEK_SET);
            char* ceros = (char*) malloc(sumandoextra);
            i = 0;
            while(i < sumandoextra) {
                ceros[i] = 0;
                i++;
            }
            i = 0;
            char* matrizUsada;
            if(metodoBordes != 'f') {
                matrizUsada = matriz2;
            } else {
                matrizUsada = matriz;
            }
            while(i < alto) {
                int j = 0;
                while(j < ancho) {
                    fwrite(&matrizUsada[i*ancho+j], 1, 1, salida);
                    fwrite(&matrizUsada[i*ancho+j], 1, 1, salida);
                    fwrite(&matrizUsada[i*ancho+j], 1, 1, salida);

                    j++;
                }
                fwrite(ceros, sumandoextra, 1, salida);
                i++;
            }
            if(metodoBordes == 'f') {
                free(matriz);
            }

            // limpieza
            free(ceros);
            free(info);
            free(matriz2);
            fclose(fichero);
            fclose(salida);
        }
    }
    return 0;
}

